﻿//********************************************************************************************************
// Product Name: SkiingTools.ElevationProfileTool
// Description:  This tool calculates the elevation profile along a polyline from a DEM raster
//
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
//
// The Initial Developers of this Original Code are Jiri Kadlec and Tevaganthan Veluppillai. Created in January, 2010.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
// -----------------------|------------------------|--------------------------------------------
// Jiri Kadlec            |  2/6/2010              |  Cleaned up some formatting issues using re-sharper 
//********************************************************************************************************


using System;
using MapWindow.Data;
using MapWindow.Tools;
using MapWindow.Tools.Param;
using System.Data;
using System.IO;
using System.Text;
using MapWindow.Geometries;

namespace SkiingTools
{
    public class ElevationProfileTool : ITool
    {
        private string _workingPath;
        private Parameter[] _inputParam;
        private Parameter[] _outputParam;

        #region ITool Members

        /// <summary>
        /// Returns the Version of the tool
        /// </summary>
        public Version Version
        {
            get { return (new Version(1, 0, 0, 0)); }
        }

        /// <summary>
        /// Returns the Author of the tool's name
        /// </summary>
        public string Author
        {
            get { return ("JiriTeva"); }
        }

        /// <summary>
        /// Returns the category of tool that the ITool should be added to
        /// </summary>
        public string Category
        {
            get { return ("Skiing"); }
        }

        /// <summary>
        /// Returns a description of what the tool does for inclusion in the help section of the toolbox list
        /// </summary>
        public string Description
        {
            get { return ("Calculate elevation profile from the DEM raster along the first polyline shape in the shapefile"); }
        }

        /// <summary>
        /// Once the Parameter have been configured the Execute command can be called, it returns true if succesful
        /// </summary>
        public bool Execute(ICancelProgressHandler cancelProgressHandler)
        {
            IRaster DEMRaster = _inputParam[0].Value as IRaster;

            IFeatureSet inputFeatureSet = _inputParam[1].Value as IFeatureSet;

            string csvFileName = _outputParam[0].Value.ToString();

            return Execute(DEMRaster, inputFeatureSet, csvFileName, cancelProgressHandler);

        }
        /// <summary>
        /// Executes the elevation profile calculation
        /// </summary>
        /// <param name="demRaster">The input raster</param>
        /// <param name="lineFeatureSet">The output raster</param>
        /// <param name="csvFileName">The name of the .csv file where the result distance versus elevation
        /// table is saved.</param>
        /// <param name="cancelProgressHandler">The progress handler</param>
        /// <returns>true if execution successful, false otherwise</returns>
        public bool Execute(IRaster demRaster, IFeatureSet lineFeatureSet, string csvFileName, ICancelProgressHandler cancelProgressHandler)
        {
            //Validates the input and output data
            if (demRaster == null || lineFeatureSet == null || csvFileName == null)
            {
                return false;
            }

            //Extract elevation to a data table
            DataTable table = CalculateElevationProfile(lineFeatureSet, demRaster);

            //convert the data table to csv file
            DataTable2CSV(table, csvFileName, ",");

            return true;
        }


        // '' <summary>
        // '' Calculate the elevation profile for the complete path shapefile
        // '' </summary>
        // '' <param name="shpFile"></param>
        // '' <returns></returns>
        // '' <remarks></remarks>
        public static DataTable CalculateElevationProfile(IFeatureSet shpFile, IRaster demGrid)
        {
            // (1) Create the empty result DataTable
            DataTable table = new DataTable();
            table.Columns.Add(new DataColumn("distance"));
            table.Columns.Add(new DataColumn("elevation"));
            
            // (2) Get the polyline shape and loop through each straight line section
            IFeature polyline = shpFile.Features[0];

            for (int i = 0; i < polyline.Coordinates.Count; i++)
            {
                if (i < polyline.Coordinates.Count - 1)
                {
                    Coordinate startPoint = polyline.Coordinates[i];
                    Coordinate endPoint = polyline.Coordinates[i + 1];
                    ExtractElevation(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, demGrid, table);
                }
            }
            return table;
        }

        private static void ExtractElevation(double startX, double startY, double endX, double endY, IRaster demGrid, DataTable table)
        {
            double curY = startY;
            double curX = startX;
            double curDist = 0;
            
            // we assume the number of points in the segment is 100 points
            int numberOfPoints = 100;
            // declare arrays for storing distance, elevation, slope
            double[] distanceArray = new double[numberOfPoints];
            double[] elevationArray = new double[numberOfPoints];

            double constXDif = ((endX - startX)
                        / numberOfPoints);

            double constYDif = ((endY - startY)
                        / numberOfPoints);

            double constDiagDif = Math.Sqrt((constXDif * constXDif) + (constYDif * constYDif));
            
            for (int j = 0; j < numberOfPoints; j++)                     
            {
                if ((j == 0))
                {
                    curX = startX;
                    curY = startY;
                }
                else
                {
                    curX = (curX + constXDif);
                    curY = (curY + constYDif);
                    curDist = (curDist + constDiagDif);
                }
                distanceArray[j] = curDist;
                elevationArray[j] = demGrid.GetNearestValue(curX, curY);
            }
            
            if ((table.Rows.Count == 0))
            {
                for (int i = 0; i < elevationArray.Length; i++)
                {
                    DataRow tableRow = table.NewRow();
                    tableRow[0] = distanceArray[i];
                    tableRow[1] = elevationArray[i];
                    table.Rows.Add(tableRow);
                }
            }
            else
            {
                int lastRowIndex = table.Rows.Count - 1;
                double initialDistance = Convert.ToDouble(table.Rows[lastRowIndex][0]);
                
                
                for (int i = 1; i < elevationArray.Length; i++)
                {
                    DataRow tableRow = table.NewRow();
                    tableRow[0] = (distanceArray[i] + initialDistance);
                    tableRow[1] = elevationArray[i];
                    table.Rows.Add(tableRow);
                }
            }
        }



       


        /// <summary>
        /// Image displayed in the help area when no input field is selected
        /// </summary>
        public System.Drawing.Bitmap HelpImage
        {
            get { return null; }
        }

        /// <summary>
        /// Help text to be displayed when no input field is selected
        /// </summary>
        public string HelpText
        {
            get
            {
                return ("Elevation Profile from DEM");
            }
        }

        /// <summary>
        /// Returns the address of the tools help web page in HTTP://... format. Return a empty string to hide the help hyperlink.
        /// </summary>
        public string HelpURL
        {
            get { return ("HTTP://jiriteva.codeplex.com"); }
        }


        /// <summary>
        /// 32x32 Bitmap - The Large icon that will appears in the Tool Dialog Next to the tools name
        /// </summary>
        public System.Drawing.Bitmap Icon
        {
            get { return (null); }
        }

        /// <summary>
        /// 16x16 Bitmap - The small icon that appears in the Tool Manager Tree
        /// </summary>
        public System.Drawing.Bitmap IconSmall
        {
            get { return (null); }
        }

        /// <summary>
        /// The Parameter array should be populated with default values here
        /// </summary>
        /// <returns></returns>
        public void Initialize()
        {
            _inputParam = new Parameter[2];
            _inputParam[0] = new RasterParam("DEM raster");
            _inputParam[0].HelpText = "The DEM with elevation values";
            _inputParam[1] = new LineFeatureSetParam("Line Feature Set");
            _inputParam[1].HelpText = "Polyline feature set. The elevation profile is calculated for the first polyline shape";
           
            _outputParam = new Parameter[1];
            _outputParam[0] = new SaveFilePram("Output Data Table");
            _outputParam[0].HelpText = "The output data table contains distance along path and elevation values";

        }

        /// <summary>
        /// Gets or Sets the input paramater array
        /// </summary>
        public Parameter[] InputParameters
        {
            get { return _inputParam; }
        }

        /// <summary>
        /// Returns the name of the tool
        /// </summary>
        public string Name
        {
            get { return ("Elevation Profile"); }
        }

        /// <summary>
        /// Gets or Sets the output paramater array
        /// </summary>
        public Parameter[] OutputParameters
        {
            get { return _outputParam; }
        }

        /// <summary>
        /// Fires when one of the paramters value has beend changed, usually when a user changes a input or output Parameter value, this can be used to populate input2 Parameter default values.
        /// </summary>
        void ITool.ParameterChanged(Parameter sender)
        {
            return;
        }

        /// <summary>
        /// Returns a brief description displayed when the user hovers over the tool in the toolbox
        /// </summary>
        public string ToolTip
        {
            get { return ("Calculate Elevation Profile"); }
        }

        /// <summary>
        /// A UniqueName Identifying this Tool, if aninput2 tool with the same UniqueName exists this tool will not be loaded
        /// </summary>
        public string UniqueName
        {
            get { return ("jiriTevaElevationProfile"); }
        }

        /// <summary>
        /// This is set before the tool is executed to provide a folder where the tool can save temporary data
        /// </summary>
        public string WorkingPath
        {
            set { _workingPath = value; }
        }

        #endregion

        #region Private methods
        /// <summary>
        /// This method is used to Convert DataTable to CSV ( comma seperated ) file.
        /// </summary>
        /// <param name="table">Input table which returned from the tool</param>
        /// <param name="filename">User specified filename</param>
        /// <param name="seperateChar"></param>
        public static void DataTable2CSV(DataTable table, string filename, string seperateChar)
        {
            StreamWriter sr = null;
            try
            {

                sr = new StreamWriter(filename);
                string seperator = "";
                StringBuilder builder = new StringBuilder();
                foreach (DataColumn col in table.Columns)
                {

                    builder.Append(seperator).Append(col.ColumnName);

                    seperator = seperateChar;
                }

                sr.WriteLine(builder.ToString());

                foreach (DataRow row in table.Rows)
                {

                    seperator = "";
                    builder = new StringBuilder();
                    foreach (DataColumn col in table.Columns)
                    {

                        builder.Append(seperator).Append(row[col.ColumnName]);
                        seperator = seperateChar;

                    }

                    sr.WriteLine(builder.ToString());

                }

            }

            finally
            {

                if (sr != null)
                {

                    sr.Close();

                }

            }

        } 

        #endregion
    }
}
